View Javadoc

1   /*
2    * Copyright 2004-2005, University Health Network.  All rights reserved. Distributed under the BSD 
3    * license (see http://opensource.org/licenses/bsd-license.php).
4    *  
5    * Created on 9-Dec-2004
6    */
7   package ca.uhn.cache.impl;
8   
9   import java.util.ArrayList;
10  import java.util.Arrays;
11  import java.util.Iterator;
12  import java.util.List;
13  
14  import org.apache.commons.logging.Log;
15  import org.apache.commons.logging.LogFactory;
16  import org.springframework.beans.factory.InitializingBean;
17  
18  import ca.uhn.cache.IDimension;
19  import ca.uhn.cache.IParamSpace;
20  import ca.uhn.cache.IQuery;
21  import ca.uhn.cache.IQueryParam;
22  import ca.uhn.cache.internal.IParamSpaceConfig;
23  
24  /***
25   * A multi-dimensional space of <code>IQueryParam</code>s.  A data item 
26   * has a location in this space (see <code>IDataInspector</code>).  Data 
27   * items are divided into <code>IChunk</code>s that represent regions of the 
28   * space.  
29   *   
30   * @author <a href="mailto:bryan.tripp@uhn.on.ca">Bryan Tripp</a>
31   * @version $Revision: 1.1 $ updated on $Date: 2005/01/24 22:52:39 $ by $Author: bryan_tripp $
32   */
33  public class ParamSpace implements IParamSpace, InitializingBean {
34  
35      private static final Log ourLog = LogFactory.getLog( ParamSpace.class );
36      
37      private IParamSpaceConfig myParamSpaceConfig;
38      
39      /***
40       * Factory method.
41       * 
42       * @param theParamSpaceConfig The configuration information for this space (defining dimensions, etc).
43       * @return The created <code>ParamSpace</code>. 
44       */
45      public static ParamSpace createInstance( IParamSpaceConfig theParamSpaceConfig ) {
46          ParamSpace retVal = new ParamSpace();
47          
48          retVal.setParamSpaceConfig(theParamSpaceConfig);
49          
50          return retVal;
51      }
52  
53      /***
54       */
55      public ParamSpace() {
56      }
57      
58      /***
59       * {@inheritDoc}
60       */
61      public IDimension[] getDimensions()
62      {
63          return myParamSpaceConfig.getDimensions();
64      }
65      
66      /***
67       * {@inheritDoc}
68       */
69      public IQueryParam[] chunk(IQueryParam theParam)
70      {
71          IQueryParam[] result = null;
72      
73          if (myParamSpaceConfig.isChunked(theParam.getDimension())) {
74              IQueryParam[] all = myParamSpaceConfig.getChunkBoundaries(theParam.getDimension()); 
75              
76              List intersecting = new ArrayList(); 
77              for (int i = 0; i < all.length; i++) {
78                  if (theParam.intersects(all[i])) {
79                      intersecting.add(all[i]);
80                  } 
81              }
82  
83              //TODO: should maybe check whether the result spans the original param as well as whether 
84              //there is no intersection (don't have a way to do this currently)
85              if (intersecting.size() == 0) {
86                  ourLog.warn("An IQueryParam intersects 0 chunks.  ParamSpace may not be configured properly");
87              }
88              
89              result = (IQueryParam[]) intersecting.toArray(new IQueryParam[ intersecting.size() ]);
90          } else {
91              //TODO: should maybe warn if we don't have a point param.  Range/set params won't 
92              //necessarily be consistent across calls, so chunk lookup won't work.  Consequence: 
93              //missed cache hits, unused cached data. 
94              result = new IQueryParam[] {theParam};
95          }
96  
97          return result;
98      }
99  
100     /***
101      * {@inheritDoc}
102      */
103     public IQuery[] group( IQuery[] theQueries, int theMaxGroups ) {
104         IQuery[] retVal = theQueries;
105         
106         if ( theMaxGroups > 0 ) {
107             do {
108                 retVal = doGroup(retVal, theMaxGroups);
109             } while (retVal.length > theMaxGroups);
110         }
111         
112         return retVal;
113     }
114     
115     //a single pass through a list of IQuery to group them  
116     private IQuery[] doGroup(IQuery[] theQueries, int theMaxGroups) {
117         List grouped = new ArrayList(theQueries.length);
118         List ungrouped = new ArrayList(Arrays.asList(theQueries));
119         
120         while (ungrouped.size() > 0 && (grouped.size() + ungrouped.size()) > theMaxGroups) {
121             IQuery toGroup = (IQuery) ungrouped.remove(0);
122             IQuery closestGrouped = findClosest(toGroup, grouped);
123             IQuery closestUngrouped = findClosest(toGroup, ungrouped);
124             
125             if (isCloser(toGroup, closestUngrouped, closestGrouped)) {
126                 ungrouped.remove(closestUngrouped);
127                 grouped.add(merge(toGroup, closestUngrouped));
128             } else {
129                 grouped.remove(closestGrouped);
130                 grouped.add(merge(toGroup, closestGrouped));                
131             }
132         }                
133         
134         grouped.addAll(ungrouped);
135         return (IQuery[]) grouped.toArray(new IQuery[0]);
136     }
137     
138     //compares distances from one query to two others, treating distance to null
139     //query as infinity 
140     private boolean isCloser(IQuery theFrom, IQuery theTo, IQuery theOtherTo) {        
141         if (theTo == null) {
142             return false;
143         } else if (theOtherTo == null) {
144             return true;
145         } else {
146             return (distance(theFrom, theTo) < distance(theFrom, theOtherTo)) ? true : false; 
147         }
148     }
149     
150     //results in union of two queries PLUS all space between 
151     private IQuery merge(IQuery theQuery, IQuery theOther) {
152         Query result = new Query();
153 
154         if (theQuery.getParameters().size() != theOther.getParameters().size()) {
155             throw new IllegalArgumentException("Can't merge queries with different "
156                     + "numbers of dimensions");
157         }
158         
159         Iterator params = theQuery.getParameters().iterator();
160         while (params.hasNext()) {
161             IQueryParam oneParam = (IQueryParam) params.next();
162             IQueryParam otherParam = getParam(theOther, oneParam.getDimension());
163             result.addParameter(oneParam.merge(otherParam));
164         }
165         
166         return result;
167     }
168     
169     //finds closest query in a list 
170     //TODO: can optimize by defining a minimum distance below which grouping 
171     //is considered good enough (don't have to find global optimim)
172     private IQuery findClosest(IQuery theQuery, List theQueries) {
173         float shortestDistance = 10000f;
174         IQuery closest = null;
175         
176         for (int i = 0; i < theQueries.size(); i++) {
177             float distance = distance(theQuery, (IQuery) theQueries.get(i));
178             if (distance < shortestDistance) {
179                 shortestDistance = distance;
180                 closest = (IQuery) theQueries.get(i);
181             }
182         }
183         
184         return closest;
185     }
186     
187     //TODO: could be optimized if IQuery had getParameter(IDimension)
188     private float distance(IQuery theQuery, IQuery theOtherQuery) {
189         float squaredResult = 0;
190         
191         Iterator params = theQuery.getParameters().iterator();
192         while (params.hasNext()) {
193             IQueryParam oneParam = (IQueryParam) params.next();
194             IQueryParam otherParam = getParam(theOtherQuery, oneParam.getDimension());
195             float distance = oneParam.getDistance(otherParam, 
196                     myParamSpaceConfig.getSaturationPoint(oneParam.getDimension()));
197             squaredResult += (distance * distance);
198         }
199         
200         return (float) Math.sqrt(squaredResult);
201     }
202     
203     //returns the IQueryParam in the given dimension (exception if missing)
204     private IQueryParam getParam(IQuery theQuery, IDimension theDimension) {
205         IQueryParam result = null;
206         
207         Iterator params = theQuery.getParameters().iterator();
208         while (params.hasNext() && result == null) {
209             IQueryParam param = (IQueryParam) params.next();
210             if (param.getDimension().equals(theDimension)) {
211                 result = param;
212             }
213         }
214         
215         if (result == null) {
216             throw new IllegalArgumentException("Query has no parameter of dimension "
217                     + theDimension.getName());
218         }
219         
220         return result;
221     }
222     
223     ////////// SPRING BEGIN /////////
224 
225     /***
226      * @return The configuration information for this space (defining dimensions, etc).
227      */
228     public IParamSpaceConfig getParamSpaceConfig() {
229         return myParamSpaceConfig;
230     }
231     /***
232      * @param theConfigData The configData to set.
233      */
234     public void setParamSpaceConfig( IParamSpaceConfig theConfigData ) {
235         myParamSpaceConfig = theConfigData;
236     }
237 
238     /***
239      * {@inheritDoc}
240      */
241     public void afterPropertiesSet() throws Exception {
242         assert getParamSpaceConfig() != null : "getParamSpaceConfig() != null";
243     }
244     
245     ////////// SPRING END /////////
246     
247     
248 }